/**
 ****************************************************************************
 * Copyright (C) Marcelo F. Ochoa. All rights reserved.                      *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included  with this distribution in *
 * the LICENSE file.                                                         *
 */

package com.prism.oracle;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.transaction.xa.XAException;

import oracle.jdbc.pool.OracleDataSource;
import oracle.jdbc.xa.OracleXAException;

import org.apache.log4j.Logger;
import org.jconfig.Configuration;

import com.prism.ConnInfo;
import com.prism.DBConnection;
import com.prism.DBFactory;
import com.prism.DBPrismConnectionCache;
import com.prism.TxDicc;

import javax.naming.NameNotFoundException;


/**
 * This class is a Singleton that provides access to one or many connection
 * pools defined in a Property file. A client gets access to the single instance
 * through the static getInstance() method and can then check-out and check-in
 * connections from a pool. When the client shuts down it should call the
 * release() method to close all open connections and do other clean up. Need a
 * java enabled 8.1.6+ database to run. <BR>
 *
 * @author Marcelo F. Ochoa <BR>
 * <BR>
 * Modified: 7/Oct/2004 by <a href="mailto:pyropunk@usa.net">Alexander Graesser
 * </a> (LXG) <BR>
 * Changes :
 * <UL>
 * <LI>Added log4j logging</LI>
 * <LI>JavDoc cleanup</LI>
 * <LI>code cleanup</LI>
 * </UL>
 */
public class JTADBPrismConnectionCacheImpl implements java.lang.Runnable, DBPrismConnectionCache {
    private static Logger log = Logger.getLogger(JTADBPrismConnectionCacheImpl.class);

    // LXG: never used
    // private static TXBranchManager globalBranchManager = null;
    private static int globalID = 100;

    private static int clients = 0;

    private static int connectionTimeOut;

    private static boolean txEnableProcessing;

    private static boolean bContinue = true;

    private static Hashtable BusyList;

    private static Context ConnectionCacheList;

    private static java.lang.Thread txThread = null;

    private int counter = -1;

    private java.lang.String TxId;

    private DBConnection connection;

    private static JTADBPrismConnectionCacheImpl instance = null;

    private TXBranchManager bid = null;

    private static Configuration props = null;

    /**
     * Singleton
     * 
     * @param prop Configuration
     * @return JTADBPrismConnectionCacheImpl
     * @throws Exception
     */
    public static JTADBPrismConnectionCacheImpl getInstance(Configuration prop) throws Exception {
        if (instance == null) {
            if (log.isInfoEnabled())
                log.info(".getInstance - Initializing Transaction manager...");
            instance = new JTADBPrismConnectionCacheImpl(prop);
        }
        clients++;
        return instance;
    }

    /**
     * A private constructor since this is a Singleton
     * 
     * @param prop Configuration
     * @throws Exception
     */
    private JTADBPrismConnectionCacheImpl(Configuration prop) throws Exception {
        init(prop);
    }

    /**
     * Initialize BusyList (Busy Connection) Set JTADBPrismConnectionCacheImpl
     * parameters. Start a Thread wich control age of connections and
     * transacctions
     * 
     * @param prop Configuration
     * @throws Exception
     */
    public void init(Configuration prop) throws Exception {
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            BusyList = new Hashtable();
            ConnectionCacheList = new InitialContext();
        }
        props = prop;
        txEnableProcessing = props.getBooleanProperty("TxEnable", true, "Manager");
        connectionTimeOut = props.getIntProperty("timeout", 600, "Manager");
        // Load and register Oracle JDBC driver.
        String compatList = props.getProperty("compatlist");
        StringTokenizer st = new StringTokenizer(compatList, " ");
        while (st.hasMoreElements()) {
            //Load the Oracle JDBC Driver and register it.
            String strcompat = (String) st.nextElement();
            String JdbcDriver = props.getProperty(strcompat, null, "jdbcDriver");
            try {
                Driver drv = (Driver) Class.forName(JdbcDriver).newInstance();
                DriverManager.registerDriver(drv);
            } catch (Exception e) {
                log.warn("JTADBPrismConnectionCacheImpl Warning: Can't register JDBC driver (" + JdbcDriver + ")");
            }
        }
        // Create a ConnectionCache for every DAD
        Enumeration diccList = DBConnection.getAll();
        while (diccList.hasMoreElements()) {
            ConnInfo connInfo = (ConnInfo) diccList.nextElement();
            if (!connInfo.usr.equalsIgnoreCase("")) {
                // Stores a pre-cached OracleDataSource Cache only if username is not null
                createNewConnectionCache(connInfo);
            }
        }
        // Create and Start the Thread which controls age expiration and
        // transaction
        txThread = new Thread(this);
        txThread.start();
        TXBranchManager.getInstance(props);
    }

    /**
     * replace deprecated method for jdk 1.2 Stop the tread by set to false
     * boolean var of while loop in method Run
     */
    public void stopManager() {
        bContinue = false;
    }

    /** A public constructor nothing to do here. */
    public JTADBPrismConnectionCacheImpl() {
        super();
    }

    /**
     * this method inherit from Runnable interface check every 1 second for
     * unused connection (in BusyList) if the connection is unused for
     * {Manager.timeout} seconds - close it (BusyList)
     */
    public void run() {
        Enumeration en;
        JTADBPrismConnectionCacheImpl rtmp;
        try {
            if (log.isDebugEnabled()) {
                log.debug("run: Thread  started...");
            }
            while (bContinue) {
                // Verify if there are unused connection in Busy List or
                // Transaction incompleted
                synchronized (JTADBPrismConnectionCacheImpl.class) {
                    en = BusyList.elements();
                }
                while (en.hasMoreElements()) {
                    rtmp = (JTADBPrismConnectionCacheImpl) en.nextElement();
                    if (log.isDebugEnabled()) {
                        log.debug("run: Busy " + rtmp.TxId + " count=" + rtmp.counter);
                    }
                    rtmp.counter--; // Reduce counter for checking age of
                                    // Transaction
                    if (rtmp.counter < 0) {
                        if (log.isDebugEnabled()) {
                            log.debug("run: Remove Connection from BusyList " + rtmp.TxId);
                        }
                        freeJTAResource(rtmp);
                    }
                }
                if (log.isDebugEnabled()) {
                    //log.debug("run: Wait 1 second...");
                }
                Thread.sleep(1000); // Wait 1 second
            }
            // Free all TX pooled connections
            synchronized (JTADBPrismConnectionCacheImpl.class) {
                en = BusyList.elements();
            }
            while (en.hasMoreElements()) {
                rtmp = (JTADBPrismConnectionCacheImpl) en.nextElement();
                freeJTAResource(rtmp);
            }
            synchronized (JTADBPrismConnectionCacheImpl.class) {
                BusyList.clear();
            }
            if (log.isDebugEnabled()) {
                log.debug("run: Thread  finished...");
            }
        } catch (Exception e) {
            log.warn("Can't start the TX Thread or Thread interrup", e);
        }
    }

    private OracleDataSource createNewConnectionCache(ConnInfo connInfo) throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache entered.");
        }
        // Stores a pre-cached OracleDataSource Cache only if username is not null
        OracleDataSource ocacheimpl = new OracleDataSource();
        ocacheimpl.setURL(connInfo.connectString);
        ocacheimpl.setUser(connInfo.usr);
        ocacheimpl.setPassword(connInfo.pass);
        ocacheimpl.setConnectionCachingEnabled(true);
        Properties cacheProps = new Properties();
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache Setting DAD " + connInfo.connAlias);
        }
        String value = props.getProperty("MinLimit", "0", "DAD_" + connInfo.connAlias);
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache - MinLimit: " + value);
        }
        cacheProps.put("MinLimit",value);
        value = props.getProperty("MaxLimit", "20", "DAD_" + connInfo.connAlias);
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache - MaxLimit: " + value);
        }
        cacheProps.put("MaxLimit",value);
        value = props.getProperty("LoginTimeout", "0", "DAD_" + connInfo.connAlias);
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache - LoginTimeout: " + value);
        }
        cacheProps.put("ConnectionWaitTimeout",value);
        // new in release 2.1.2
        value = props.getProperty("InitialLimit", "0", "DAD_" + connInfo.connAlias);
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache - InitialLimit: " + value);
        }
        cacheProps.put("InitialLimit",value);
        value = props.getProperty("MaxStatementsLimit", "0", "DAD_" + connInfo.connAlias);
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache - MaxStatementsLimit: " + value);
        }
        cacheProps.put("MaxStatementsLimit",value);
        value = props.getProperty("ValidateConnection", "false", "DAD_" + connInfo.connAlias);
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache - ValidateConnection: " + value);
        }
        cacheProps.put("ValidateConnection",value);
        // InactivityTimeout is calculated using connectionTimeOut
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache - InactivityTimeout: " + 20);
        }
        cacheProps.put("InactivityTimeout",""+(connectionTimeOut+20));
        ocacheimpl.setConnectionCacheProperties(cacheProps);
        // Stores a pre-cached connection object identified by
        // "alias"+username+password
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            try {
                ConnectionCacheList.bind(connInfo.connAlias + connInfo.usr.toUpperCase() + connInfo.pass, ocacheimpl);
            } catch (NamingException e) {
                log.error(".createNewConnectionCache - Can't bind OracleDataSource to the context",e);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug(".createNewConnectionCache return new Oracle connection.");
        }
        return ocacheimpl;
    }

    /**
     * Create a direct connection to an particular DBConnection this method is a
     * Client in Abstract Factory pattern get a correct Factory from ConnInfo
     * object a call to createDBConnection
     * 
     * @param cc_tmp ConnInfo
     * @param usr String
     * @param pass String
     * @throws SQLException
     */
    public JTADBPrismConnectionCacheImpl(ConnInfo cc_tmp, String usr, String pass) throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("creating new object.");
        }
        OracleDataSource ocacheimpl = null;
        DBFactory factory = cc_tmp.getFactory();
        connection = factory.createDBConnection(cc_tmp); // Allocated
                                                         // DBConnection
        TxId = connection.toString(); // Store connection id as TxId
        connection.connInfo = cc_tmp; // Set default values for ConnInfo object
        connection.connInfo.txEnable = false; // set as direct connection
        connection.connInfo.status = ConnInfo.CONN_DIR;
        connection.connInfo.usr = usr;
        connection.connInfo.pass = pass;
        connection.connInfo.connectString = cc_tmp.connectString;
        if (log.isDebugEnabled()) {
            log.debug("getting Oracle connection.");
        }
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            try {
                ocacheimpl = (OracleDataSource) ConnectionCacheList.lookup(connection.connInfo.connAlias + usr.toUpperCase() + pass);
            } catch (NameNotFoundException e) {
                log.debug(".JTADBPrismConnectionCacheImpl -  creating and registering a new connection cache data source");
            } catch (NamingException e) {
                log.warn(".JTADBPrismConnectionCacheImpl - Can't lookup connection '"+connection.connInfo.connAlias + usr.toUpperCase() + pass+"', it will create a new one",e);
                throw new SQLException(".JTADBPrismConnectionCacheImpl - Failed to get a datasource: "+e.getMessage());
            }
        }
        if (ocacheimpl == null) {
            ocacheimpl = createNewConnectionCache(connection.connInfo);
        }
        connection.sqlconn = ocacheimpl.getConnection();
        if (connection.sqlconn == null) {
            log.warn("Maximum number of connections for DAD " + cc_tmp.connAlias + " reached");
            throw new SQLException("Maximum number of connections for DAD " + cc_tmp.connAlias + " reached");
        }
        counter = connectionTimeOut; // How much wait for unused connection
        connection.sqlconn.setAutoCommit(false);
        if (log.isDebugEnabled()) {
            log.debug("Connect without auto commit " + TxId);
        }
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            BusyList.put(TxId, this);
        }
        if (log.isDebugEnabled()) {
            log.debug("created new object.");
        }
    }

    /**
     * Create a XA connection to an particular DBConnection this method is a
     * Client in Abstract Factory pattern get a correct Factory from ConnInfo
     * object a call to createDBConnection
     * 
     * @param cc_tmp ConnInfo
     * @param usr String
     * @param pass String
     * @param timeOut int
     * @param id String
     * @throws SQLException
     */
    public JTADBPrismConnectionCacheImpl(ConnInfo cc_tmp, String usr, String pass, int timeOut, String id) throws SQLException {
        if (timeOut >= connectionTimeOut) {
            log.warn("JTADBPrismConnectionCacheImpl: Can't start a transaction with its timeout greater than Manager.timeout value");
            throw new SQLException("JTADBPrismConnectionCacheImpl: Can't start a transaction with its timeout greater than Manager.timeout value");
        }
        if (log.isDebugEnabled()) {
            log.debug("creating new object.");
        }
        DBFactory factory = cc_tmp.getFactory();
        connection = factory.createDBConnection(cc_tmp); // Allocated
                                                         // DBConnection
        counter = timeOut; // How much wait for unused transaction
        connection.connInfo = cc_tmp; // Set default values for ConnInfo object
        connection.connInfo.txEnable = true; // begun as direct connection
        TxId = id; // Store Session id as TxId
        connection.connInfo.status = ConnInfo.CONN_TX;
        connection.connInfo.usr = usr;
        connection.connInfo.pass = pass;
        connection.connInfo.connectString = cc_tmp.connectString;
        globalID = (globalID + 100) % 32768;
        if (log.isDebugEnabled())
            log.debug("Global ID=" + globalID);
        bid = new TXBranchManager(globalID);
        // sqlconn will taked at start point
        connection.sqlconn = null;
        if (log.isDebugEnabled())
            log.debug("Connect without XA resource " + TxId);
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            BusyList.put(TxId, this);
        }
        if (log.isDebugEnabled()) {
            log.debug("created new object.");
        }
    }

    /**
     * Implements method get from interface DBPrismResource Get one Connection
     * from OracleDataSource
     * 
     * @param req HttpServletRequest
     * @param usr String
     * @param pass String
     * @return DBConnection
     * @throws SQLException
     */
    public DBConnection get(HttpServletRequest req, String usr, String pass) throws SQLException {
        JTADBPrismConnectionCacheImpl txtmp = null;
        ConnInfo cc_tmp = new ConnInfo(req);
        if (log.isDebugEnabled()) {
            log.debug(".get entered.");
        }
        if (txEnableProcessing) {
            // Process session Enable behavior
            // Verify if the URL is part of a TxDef
            if (log.isDebugEnabled()) {
                log.debug(".get Verify the URL: " + req.getRequestURI());
            }
            TxDicc ctx = TxDicc.getTxDef(req.getRequestURI());
            if (ctx == null) {
                // if the URL is not part of any Transaction definition return a
                // normal connection
                txtmp = new JTADBPrismConnectionCacheImpl(cc_tmp, usr, pass);
                if (log.isDebugEnabled()) {
                    log.debug(".get Return normal connection");
                }
                return txtmp.connection;
            }
            // Get Session ID from de cookie session
            HttpSession ss = req.getSession(false);
            if (ss != null) {
                // if there is session id get the active connection from
                // BusyList
                JTADBPrismConnectionCacheImpl rtmp;
                synchronized (JTADBPrismConnectionCacheImpl.class) {
                    rtmp = (JTADBPrismConnectionCacheImpl) BusyList.get(ss.getId());
                }
                if (rtmp == null) { // if there aren't Session Id in hashtable
                    try {
                        ss.invalidate(); // Invalidate the session ID, remove
                                         // the cookie
                    } catch (Exception e) {
                        log.warn(".get - Error invalidating transaction cookie", e);
                    }
                    throw new SQLException("Invalid Session ID=" + ss.getId());
                } else
                    // return the connection if the execution path is valid
                    switch (ctx.lastComp) {
                    case TxDicc.CMMT: // Commit_URI
                    case TxDicc.ROLL: // Rollback_URI
                    case TxDicc.BLNG: // Belong_TO URI
                        // Return the connection, the action take efect at
                        // release method.
                        // Resume the global transaction
                        try {
                            rtmp.connection.sqlconn = rtmp.bid.resume();
                            rtmp.counter = ctx.timeOut;
                        } catch (XAException xae) {
                            ss.invalidate(); // Invalidate the session ID,
                                             // remove the cookie
                            log.warn(".get can't resume XA transaction due to: ", xae);
                            throw new SQLException("JTADBPrismConnectionCacheImpl: can't resume XA transaction due to: " + formatErrorMsg(xae));
                        }
                        if (log.isDebugEnabled()) {
                            log.debug(".get Return TX connection (commit/rollback/belong to)");
                        }
                        return rtmp.connection;
                    case TxDicc.BGN: // Transaction already started resume it
                        try {
                            rtmp.counter = ctx.timeOut;
                            rtmp.connection.sqlconn = rtmp.bid.resume();
                        } catch (XAException xae) {
                            ss.invalidate(); // Invalidate the session ID,
                                             // remove the cookie
                            log.warn(".get can't resume XA transaction due to: ", xae);
                            throw new SQLException("JTADBPrismConnectionCacheImpl: can't start or resume XA transaction due to: " + formatErrorMsg(xae));
                        }
                        if (log.isDebugEnabled()) {
                            log.debug(".get Return TX connection (begin/already started)");
                        }
                        return rtmp.connection;
                    } // end switch type of URI
            } else { // if there isn't ssesion id in the request verify the
                     // Begin of Tx
                if (ctx.lastComp == TxDicc.BGN) { // if is type Begin_URL
                    // Create a Session id object to identified this transaction
                    ss = req.getSession(true);
                    try {
                        txtmp = new JTADBPrismConnectionCacheImpl(cc_tmp, usr, pass, ctx.timeOut, ss.getId());
                        txtmp.connection.sqlconn = txtmp.bid.start(cc_tmp.connectString, ctx.txName, usr, pass);
                    } catch (XAException xae) { // XA Problems
                        if (txtmp != null) // free allocated branch manager
                            txtmp.bid = null;
                        // JTADBPrismConnectionCacheImpl's constructor never
                        // fail, then connection was
                        // already putted into BusyList
                        // start could be failed then remove from BusyList
                        synchronized (JTADBPrismConnectionCacheImpl.class) {
                            try {
                                BusyList.remove(txtmp.TxId);
                            } catch (Exception e) {
                                // Ignore this exception
                                log.warn(".get Ignoring exception when remove from BusyList", e);
                            }
                        }
                        ss.invalidate(); // Invalidate the session ID, remove
                                         // the cookie
                        log.warn(".get can't start XA transaction due to: ", xae);
                        throw new SQLException("JTADBPrismConnectionCacheImpl: can't start XA transaction due to: " + formatErrorMsg(xae));
                    } catch (SQLException sqe) { // SQL problems
                        if (txtmp != null) // free allocated branch manager
                            txtmp.bid = null;
                        // JTADBPrismConnectionCacheImpl's constructor never
                        // fail, then connection was
                        // already inserted into BusyList
                        // start could be failed then remove from BusyList
                        synchronized (JTADBPrismConnectionCacheImpl.class) {
                            try {
                                BusyList.remove(txtmp.TxId);
                            } catch (Exception e) {
                                // Ignore this exception
                                log.warn(".get Ignoring exception when remove from BusyList", e);
                            }
                        }
                        ss.invalidate(); // Invalidate the session ID, remove
                                         // the cookie
                        log.warn(".get there where SQL Error starting the transaction", sqe);
                        throw sqe;
                    }
                    // reset packages states
                    txtmp.connection.resetPackages();
                    if (log.isDebugEnabled()) {
                        log.debug(".get Return TX connection (begin/new connection)");
                    }
                    return txtmp.connection;
                } // end if Begin transaction
            } // end if ss!=null
        } // end if txEnable
        // If not TxEnable mode return a normal connection
        txtmp = new JTADBPrismConnectionCacheImpl(cc_tmp, usr, pass);
        if (log.isDebugEnabled()) {
            log.debug(".get Return normal connection");
        }
        return txtmp.connection;
    }

    /**
     * Implements method release from interface DBPrismResource Shutdown
     * JTADBPrismConnectionCacheImpl
     * 
     * @throws SQLException
     */
    public void release() throws SQLException {
        Enumeration e;
        if (log.isDebugEnabled()) {
            log.debug(".release client: " + clients);
        }
        stopManager();
        //System.out.println("Thread stoped");
        // Wait for the thread finished
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            //System.out.println("Freeing Busy Connections");
            e = BusyList.elements();
            while (e.hasMoreElements()) {
                JTADBPrismConnectionCacheImpl ctmp = (JTADBPrismConnectionCacheImpl) e.nextElement();
                if (ctmp.connection.connInfo.txEnable) {
                    ctmp.bid.release();
                } else if (ctmp.connection.sqlconn != null && !ctmp.connection.sqlconn.isClosed()) {
                    ctmp.connection.sqlconn.rollback();
                    ctmp.connection.sqlconn.close();
                }
                //System.out.println("BusyList all released");
            }
            BusyList.clear();
        }
        clients--;
    }

    /**
     * Implements method release from interface DBPrismResource Release one
     * Connection
     * 
     * @param req HttpServletRequest
     * @param conn DBConnection
     * @throws SQLException
     * @throws NullPointerException
     */
    public void release(HttpServletRequest req, DBConnection conn) throws SQLException, NullPointerException {
        JTADBPrismConnectionCacheImpl rtmp;
        if (txEnableProcessing) {
            // Process the session Enable behavior
            // Verify if the URL is part of a TxDef
            if (log.isDebugEnabled()) {
                log.debug(".release - Verify the URL =" + req.getRequestURI());
            }
            TxDicc ctx = TxDicc.getTxDef(req.getRequestURI());
            if (ctx == null) {
                // if the URL is not part of any Transaction definition release
                // a normal connection
                if (log.isDebugEnabled()) {
                    log.debug(".release - releasing normal connection from JTADBPrismConnectionCacheImpl");
                }
                synchronized (JTADBPrismConnectionCacheImpl.class) {
                    rtmp = (JTADBPrismConnectionCacheImpl) BusyList.get(conn.toString());
                }
                freeJTAResource(rtmp);
                return;
            }
            // Get Session ID from de cookie session
            HttpSession ss = req.getSession(false);
            if (ss != null) { // there are a cookie session ID verify the type
                              // of URI
                // if there are session id get the active connection from
                // hastable
                synchronized (JTADBPrismConnectionCacheImpl.class) {
                    rtmp = (JTADBPrismConnectionCacheImpl) BusyList.get(ss.getId());
                }
                if (rtmp == null) { // if there aren't Session Id in hastable
                                    // invalidate the request
                    ss.invalidate(); // Invalidate the session ID, remove the
                                     // cookie
                    log.warn("Invalid Session ID=" + ss.getId());
                    throw new SQLException("Invalid Session ID=" + ss.getId());
                } else {
                    // return the connection
                    switch (ctx.lastComp) {
                    case TxDicc.CMMT: // Commit transaccion
                        if (log.isDebugEnabled()) {
                            log.debug(".release - Commit the transaccion" + rtmp.TxId);
                        }
                        // Stop the Thread, remove the transaccion from active
                        // and commit
                        // End the branch
                        // Do a 1 phase commit
                        try {
                            rtmp.bid.commit();
                        } catch (XAException xae) {
                            // if there are errors
                            ss.invalidate(); // Invalidate the session, remove
                                             // the cookie
                            log.warn(".release can't commit XA transaction due to: ", xae);
                            throw new SQLException("JTADBPrismConnectionCacheImpl: can't commit XA transaction due to: " + formatErrorMsg(xae));
                        }
                        // free logical resources
                        freeJTAResource(rtmp);
                        ss.invalidate(); // Invalidate the session, remove the
                                         // cookie
                        return;
                    case TxDicc.ROLL: // Rollback transaccion
                        if (log.isDebugEnabled()) {
                            log.debug(".release - Rollback the transaccion" + rtmp.TxId);
                        }
                        // Rollback, Stop the Thread and remove the transaccion
                        // from active
                        try {
                            rtmp.bid.rollback();
                        } catch (XAException xae) {
                            // if there are errors
                            ss.invalidate(); // Invalidate the session, remove
                                             // the cookie
                            log.warn(".release can't rollback XA transaction due to: ", xae);
                            throw new SQLException("JTADBPrismConnectionCacheImpl: can't rollback XA transaction due to: " + formatErrorMsg(xae));
                        }
                        // free logical resources
                        freeJTAResource(rtmp);
                        ss.invalidate(); // Invalidate the session, remove the
                                         // cookie
                        return;
                    default: // Belong_TO or Begin URI, reset the counter
                        // Suspend the global transaction
                        if (log.isDebugEnabled()) {
                            log.debug(".release - Suspend the transaccion" + rtmp.TxId);
                        }
                        try {
                            rtmp.bid.suspend();
                            rtmp.counter = ctx.timeOut;
                        } catch (XAException xae) {
                            // if there are errors
                            ss.invalidate(); // Invalidate the session, remove
                                             // the cookie
                            log.warn(".release can't suspend XA transaction due to: ", xae);
                            throw new SQLException("JTADBPrismConnectionCacheImpl: can't suspend XA transaction due to: " + formatErrorMsg(xae));
                        }
                        return;
                    } // end switch type of URI
                }
            } else { // there aren't session ID release a normal connection
                if (log.isDebugEnabled()) {
                    log.debug(".release - releasing normal connection from JTADBPrismConnectionCacheImpl");
                }
                synchronized (JTADBPrismConnectionCacheImpl.class) {
                    rtmp = (JTADBPrismConnectionCacheImpl) BusyList.get(conn.toString());
                }
                freeJTAResource(rtmp);
                return;
            } // end if ss!= null
        } // end if txEnable
        if (log.isDebugEnabled()) {
            log.debug(".release - releasing normal connection from JTADBPrismConnectionCacheImpl");
        }
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            rtmp = (JTADBPrismConnectionCacheImpl) BusyList.get(conn.toString());
        }
        freeJTAResource(rtmp);
    }

    /**
     * Release one Connection
     * 
     * @param res JTADBPrismConnectionCacheImpl
     * @throws SQLException
     * @throws NullPointerException
     */
    public void freeJTAResource(JTADBPrismConnectionCacheImpl res) throws SQLException, NullPointerException {
        if (res == null) { // if res is null the connection already was released
            log.warn(".freeJTAResource - Warning: attempt to release a null connection... ");
            return;
        }
        if (!res.connection.connInfo.txEnable) {
            // Discard uncommited changes
            // Remove from BusyList
            res.connection.sqlconn.close();
            if (log.isDebugEnabled()) {
                log.debug(".freeJTAResource (close)... " + res.TxId);
            }
        } // physical TX enable connection are removed by TXBranchManager class
        synchronized (JTADBPrismConnectionCacheImpl.class) {
            BusyList.remove(res.TxId);
        }
    }

    public String formatErrorMsg(XAException xae) {
        String msg = "";
        if (xae instanceof OracleXAException)
            msg = "\n   XA error is " + ((OracleXAException) xae).getXAError() + "\n   SQL error is " + ((OracleXAException) xae).getOracleError();
        //xae.printStackTrace();
        return msg;
    }
}